home *** CD-ROM | disk | FTP | other *** search
/ Freelog 100 / FreelogNo100-NovembreDecembre2010.iso / Musique / solfege / solfege-win32-3.17.0.exe / {app} / bin / Lib / CGIHTTPServer.py < prev    next >
Text File  |  2007-12-10  |  13KB  |  363 lines

  1. """CGI-savvy HTTP Server.
  2.  
  3. This module builds on SimpleHTTPServer by implementing GET and POST
  4. requests to cgi-bin scripts.
  5.  
  6. If the os.fork() function is not present (e.g. on Windows),
  7. os.popen2() is used as a fallback, with slightly altered semantics; if
  8. that function is not present either (e.g. on Macintosh), only Python
  9. scripts are supported, and they are executed by the current process.
  10.  
  11. In all cases, the implementation is intentionally naive -- all
  12. requests are executed sychronously.
  13.  
  14. SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
  15. -- it may execute arbitrary Python code or external programs.
  16.  
  17. Note that status code 200 is sent prior to execution of a CGI script, so
  18. scripts cannot send other status codes such as 302 (redirect).
  19. """
  20.  
  21.  
  22. __version__ = "0.4"
  23.  
  24. __all__ = ["CGIHTTPRequestHandler"]
  25.  
  26. import os
  27. import sys
  28. import urllib
  29. import BaseHTTPServer
  30. import SimpleHTTPServer
  31. import select
  32.  
  33.  
  34. class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
  35.  
  36.     """Complete HTTP server with GET, HEAD and POST commands.
  37.  
  38.     GET and HEAD also support running CGI scripts.
  39.  
  40.     The POST command is *only* implemented for CGI scripts.
  41.  
  42.     """
  43.  
  44.     # Determine platform specifics
  45.     have_fork = hasattr(os, 'fork')
  46.     have_popen2 = hasattr(os, 'popen2')
  47.     have_popen3 = hasattr(os, 'popen3')
  48.  
  49.     # Make rfile unbuffered -- we need to read one line and then pass
  50.     # the rest to a subprocess, so we can't use buffered input.
  51.     rbufsize = 0
  52.  
  53.     def do_POST(self):
  54.         """Serve a POST request.
  55.  
  56.         This is only implemented for CGI scripts.
  57.  
  58.         """
  59.  
  60.         if self.is_cgi():
  61.             self.run_cgi()
  62.         else:
  63.             self.send_error(501, "Can only POST to CGI scripts")
  64.  
  65.     def send_head(self):
  66.         """Version of send_head that support CGI scripts"""
  67.         if self.is_cgi():
  68.             return self.run_cgi()
  69.         else:
  70.             return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
  71.  
  72.     def is_cgi(self):
  73.         """Test whether self.path corresponds to a CGI script.
  74.  
  75.         Return a tuple (dir, rest) if self.path requires running a
  76.         CGI script, None if not.  Note that rest begins with a
  77.         slash if it is not empty.
  78.  
  79.         The default implementation tests whether the path
  80.         begins with one of the strings in the list
  81.         self.cgi_directories (and the next character is a '/'
  82.         or the end of the string).
  83.  
  84.         """
  85.  
  86.         path = self.path
  87.  
  88.         for x in self.cgi_directories:
  89.             i = len(x)
  90.             if path[:i] == x and (not path[i:] or path[i] == '/'):
  91.                 self.cgi_info = path[:i], path[i+1:]
  92.                 return True
  93.         return False
  94.  
  95.     cgi_directories = ['/cgi-bin', '/htbin']
  96.  
  97.     def is_executable(self, path):
  98.         """Test whether argument path is an executable file."""
  99.         return executable(path)
  100.  
  101.     def is_python(self, path):
  102.         """Test whether argument path is a Python script."""
  103.         head, tail = os.path.splitext(path)
  104.         return tail.lower() in (".py", ".pyw")
  105.  
  106.     def run_cgi(self):
  107.         """Execute a CGI script."""
  108.         path = self.path
  109.         dir, rest = self.cgi_info
  110.  
  111.         i = path.find('/', len(dir) + 1)
  112.         while i >= 0:
  113.             nextdir = path[:i]
  114.             nextrest = path[i+1:]
  115.  
  116.             scriptdir = self.translate_path(nextdir)
  117.             if os.path.isdir(scriptdir):
  118.                 dir, rest = nextdir, nextrest
  119.                 i = path.find('/', len(dir) + 1)
  120.             else:
  121.                 break
  122.  
  123.         # find an explicit query string, if present.
  124.         i = rest.rfind('?')
  125.         if i >= 0:
  126.             rest, query = rest[:i], rest[i+1:]
  127.         else:
  128.             query = ''
  129.  
  130.         # dissect the part after the directory name into a script name &
  131.         # a possible additional path, to be stored in PATH_INFO.
  132.         i = rest.find('/')
  133.         if i >= 0:
  134.             script, rest = rest[:i], rest[i:]
  135.         else:
  136.             script, rest = rest, ''
  137.  
  138.         scriptname = dir + '/' + script
  139.         scriptfile = self.translate_path(scriptname)
  140.         if not os.path.exists(scriptfile):
  141.             self.send_error(404, "No such CGI script (%r)" % scriptname)
  142.             return
  143.         if not os.path.isfile(scriptfile):
  144.             self.send_error(403, "CGI script is not a plain file (%r)" %
  145.                             scriptname)
  146.             return
  147.         ispy = self.is_python(scriptname)
  148.         if not ispy:
  149.             if not (self.have_fork or self.have_popen2 or self.have_popen3):
  150.                 self.send_error(403, "CGI script is not a Python script (%r)" %
  151.                                 scriptname)
  152.                 return
  153.             if not self.is_executable(scriptfile):
  154.                 self.send_error(403, "CGI script is not executable (%r)" %
  155.                                 scriptname)
  156.                 return
  157.  
  158.         # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
  159.         # XXX Much of the following could be prepared ahead of time!
  160.         env = {}
  161.         env['SERVER_SOFTWARE'] = self.version_string()
  162.         env['SERVER_NAME'] = self.server.server_name
  163.         env['GATEWAY_INTERFACE'] = 'CGI/1.1'
  164.         env['SERVER_PROTOCOL'] = self.protocol_version
  165.         env['SERVER_PORT'] = str(self.server.server_port)
  166.         env['REQUEST_METHOD'] = self.command
  167.         uqrest = urllib.unquote(rest)
  168.         env['PATH_INFO'] = uqrest
  169.         env['PATH_TRANSLATED'] = self.translate_path(uqrest)
  170.         env['SCRIPT_NAME'] = scriptname
  171.         if query:
  172.             env['QUERY_STRING'] = query
  173.         host = self.address_string()
  174.         if host != self.client_address[0]:
  175.             env['REMOTE_HOST'] = host
  176.         env['REMOTE_ADDR'] = self.client_address[0]
  177.         authorization = self.headers.getheader("authorization")
  178.         if authorization:
  179.             authorization = authorization.split()
  180.             if len(authorization) == 2:
  181.                 import base64, binascii
  182.                 env['AUTH_TYPE'] = authorization[0]
  183.                 if authorization[0].lower() == "basic":
  184.                     try:
  185.                         authorization = base64.decodestring(authorization[1])
  186.                     except binascii.Error:
  187.                         pass
  188.                     else:
  189.                         authorization = authorization.split(':')
  190.                         if len(authorization) == 2:
  191.                             env['REMOTE_USER'] = authorization[0]
  192.         # XXX REMOTE_IDENT
  193.         if self.headers.typeheader is None:
  194.             env['CONTENT_TYPE'] = self.headers.type
  195.         else:
  196.             env['CONTENT_TYPE'] = self.headers.typeheader
  197.         length = self.headers.getheader('content-length')
  198.         if length:
  199.             env['CONTENT_LENGTH'] = length
  200.         accept = []
  201.         for line in self.headers.getallmatchingheaders('accept'):
  202.             if line[:1] in "\t\n\r ":
  203.                 accept.append(line.strip())
  204.             else:
  205.                 accept = accept + line[7:].split(',')
  206.         env['HTTP_ACCEPT'] = ','.join(accept)
  207.         ua = self.headers.getheader('user-agent')
  208.         if ua:
  209.             env['HTTP_USER_AGENT'] = ua
  210.         co = filter(None, self.headers.getheaders('cookie'))
  211.         if co:
  212.             env['HTTP_COOKIE'] = ', '.join(co)
  213.         # XXX Other HTTP_* headers
  214.         # Since we're setting the env in the parent, provide empty
  215.         # values to override previously set values
  216.         for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
  217.                   'HTTP_USER_AGENT', 'HTTP_COOKIE'):
  218.             env.setdefault(k, "")
  219.         os.environ.update(env)
  220.  
  221.         self.send_response(200, "Script output follows")
  222.  
  223.         decoded_query = query.replace('+', ' ')
  224.  
  225.         if self.have_fork:
  226.             # Unix -- fork as we should
  227.             args = [script]
  228.             if '=' not in decoded_query:
  229.                 args.append(decoded_query)
  230.             nobody = nobody_uid()
  231.             self.wfile.flush() # Always flush before forking
  232.             pid = os.fork()
  233.             if pid != 0:
  234.                 # Parent
  235.                 pid, sts = os.waitpid(pid, 0)
  236.                 # throw away additional data [see bug #427345]
  237.                 while select.select([self.rfile], [], [], 0)[0]:
  238.                     if not self.rfile.read(1):
  239.                         break
  240.                 if sts:
  241.                     self.log_error("CGI script exit status %#x", sts)
  242.                 return
  243.             # Child
  244.             try:
  245.                 try:
  246.                     os.setuid(nobody)
  247.                 except os.error:
  248.                     pass
  249.                 os.dup2(self.rfile.fileno(), 0)
  250.                 os.dup2(self.wfile.fileno(), 1)
  251.                 os.execve(scriptfile, args, os.environ)
  252.             except:
  253.                 self.server.handle_error(self.request, self.client_address)
  254.                 os._exit(127)
  255.  
  256.         elif self.have_popen2 or self.have_popen3:
  257.             # Windows -- use popen2 or popen3 to create a subprocess
  258.             import shutil
  259.             if self.have_popen3:
  260.                 popenx = os.popen3
  261.             else:
  262.                 popenx = os.popen2
  263.             cmdline = scriptfile
  264.             if self.is_python(scriptfile):
  265.                 interp = sys.executable
  266.                 if interp.lower().endswith("w.exe"):
  267.                     # On Windows, use python.exe, not pythonw.exe
  268.                     interp = interp[:-5] + interp[-4:]
  269.                 cmdline = "%s -u %s" % (interp, cmdline)
  270.             if '=' not in query and '"' not in query:
  271.                 cmdline = '%s "%s"' % (cmdline, query)
  272.             self.log_message("command: %s", cmdline)
  273.             try:
  274.                 nbytes = int(length)
  275.             except (TypeError, ValueError):
  276.                 nbytes = 0
  277.             files = popenx(cmdline, 'b')
  278.             fi = files[0]
  279.             fo = files[1]
  280.             if self.have_popen3:
  281.                 fe = files[2]
  282.             if self.command.lower() == "post" and nbytes > 0:
  283.                 data = self.rfile.read(nbytes)
  284.                 fi.write(data)
  285.             # throw away additional data [see bug #427345]
  286.             while select.select([self.rfile._sock], [], [], 0)[0]:
  287.                 if not self.rfile._sock.recv(1):
  288.                     break
  289.             fi.close()
  290.             shutil.copyfileobj(fo, self.wfile)
  291.             if self.have_popen3:
  292.                 errors = fe.read()
  293.                 fe.close()
  294.                 if errors:
  295.                     self.log_error('%s', errors)
  296.             sts = fo.close()
  297.             if sts:
  298.                 self.log_error("CGI script exit status %#x", sts)
  299.             else:
  300.                 self.log_message("CGI script exited OK")
  301.  
  302.         else:
  303.             # Other O.S. -- execute script in this process
  304.             save_argv = sys.argv
  305.             save_stdin = sys.stdin
  306.             save_stdout = sys.stdout
  307.             save_stderr = sys.stderr
  308.             try:
  309.                 save_cwd = os.getcwd()
  310.                 try:
  311.                     sys.argv = [scriptfile]
  312.                     if '=' not in decoded_query:
  313.                         sys.argv.append(decoded_query)
  314.                     sys.stdout = self.wfile
  315.                     sys.stdin = self.rfile
  316.                     execfile(scriptfile, {"__name__": "__main__"})
  317.                 finally:
  318.                     sys.argv = save_argv
  319.                     sys.stdin = save_stdin
  320.                     sys.stdout = save_stdout
  321.                     sys.stderr = save_stderr
  322.                     os.chdir(save_cwd)
  323.             except SystemExit, sts:
  324.                 self.log_error("CGI script exit status %s", str(sts))
  325.             else:
  326.                 self.log_message("CGI script exited OK")
  327.  
  328.  
  329. nobody = None
  330.  
  331. def nobody_uid():
  332.     """Internal routine to get nobody's uid"""
  333.     global nobody
  334.     if nobody:
  335.         return nobody
  336.     try:
  337.         import pwd
  338.     except ImportError:
  339.         return -1
  340.     try:
  341.         nobody = pwd.getpwnam('nobody')[2]
  342.     except KeyError:
  343.         nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
  344.     return nobody
  345.  
  346.  
  347. def executable(path):
  348.     """Test for executable file."""
  349.     try:
  350.         st = os.stat(path)
  351.     except os.error:
  352.         return False
  353.     return st.st_mode & 0111 != 0
  354.  
  355.  
  356. def test(HandlerClass = CGIHTTPRequestHandler,
  357.          ServerClass = BaseHTTPServer.HTTPServer):
  358.     SimpleHTTPServer.test(HandlerClass, ServerClass)
  359.  
  360.  
  361. if __name__ == '__main__':
  362.     test()
  363.